ホームに戻る
出典 :
関連 :
目次 :
強い型付けの列挙型とは
C++11で追加された、型安全な(クラス化された)列挙体のことを指す。
従来の列挙型 (enum)
enum は型安全ではなく、列挙値は暗黙的に整数型への変換が可能であった。
また、同一スコープに存在する二つの異なる列挙体が、同じ名前のメンバを持つことができなかった。
// 列挙型の定義#1
enum E_Enumeration01
{
VAL1, //< == 0
VAL2,
VAL3 = 100,
VAL4, //< == 101
};
// 列挙型の定義#2
// ⇒ メンバ名が E_Enumeration01 と重複しているためエラーとなる
enum E_Enumeration02
{
VAL1,
VAL2,
};
void DoSomething()
{
// int型への暗黙的変換
int i = VAL1;
}
強い型付けの列挙型(スコープを持つ列挙型) (enum class / enum struct)
// 列挙型の定義#1 (enum class は enum struct に代替可能)
enum class E_Enumeration01
{
VAL1, //< == 0
VAL2,
VAL3 = 100,
VAL4, //< == 101
};
// 列挙型の定義#2
// ⇒ E_Enumeration01 と共通のメンバ名を持つことができる
enum class E_Enumeration02
{
VAL1,
VAL2,
};
void DoSomething()
{
// int型への暗黙的変換は行われない ⇒ エラー
int i = E_Enumeration01::VAL1;
}
C++11では従来の列挙型の欠点を改善すべく、「強い型付けの列挙型」が追加された。
この列挙型は暗黙的に整数値に変換されることは無く、整数値や他の列挙型との直接比較はできない。
列挙値へのアクセスは E_Enumeration::VAL1 のように、スコープを明示する必要がある 。(C#における列挙型と同様)
列挙値(のラベル)は型に拘束されるため、異なる列挙型で同じ名前のメンバを持つことができる。
なお、enum class と enum struct に機能の違いは無い。
注意が必要な点
強い型付けの列挙型( enum class )は上述のように、整数値に暗黙的変換されることがない。
このため、配列のインデクスなど整数値として用いる際は通常、キャストが必要となる。
キャストを用いれば相互変換も可能である。
// 従来の列挙型
enum Idx_Weak
{
VAL1,
VAL2,
:
};
// 強い型付けの列挙型
enum class Idx_Strong
{
VAL1, //< 0
VAL2, //< 1
:
};
// C準拠の配列
char array[] = { 'a', 'b', 'c' };
void DoSomething()
{
// enum の列挙子を配列のインデクスとして用いる ⇒ OK
char c1 = array[VAL1];
// enum class の列挙子を配列のインデクスとして用いる ⇒ NG
// char c2 = array[Idx_Strong::VAL1];
// enum class の列挙子を int に変換後、配列のインデクスとして用いる ⇒ OK
char c3 = array[static_cast<int>(Idx_Strong::VAL1)];
// int から enum class への変換 ⇒ 1 に相当する列挙子( Idx_Strong::VAL2 )が存在するためOK
Idx_Strong val = static_cast<Idx_Strong>(1);
}
列挙型の基底型
列挙型の定義時に、基底型を指定することができる。
各列挙子の内部値が基底型で表現されるため、列挙子が少ない場合は基底型を小さくすることでメモリ使用量を削減できる。
列挙子の値が基底型の範囲に収まらない場合はエラーとなる。
// 基底型を unsigned char とする
enum class Color : unsigned char
{
Red,
Green,
Blue,
};
基底型の指定は enum class だけでなく従来の enum に対しても行うことができる。
基底型を指定しない場合の振舞は両者で異なり、
- enum class : int に固定
- enum : その列挙型の全列挙子を表現できる整数型のいずれか
となる。
基底型の取得
std::underlying_type<T>を使用することで、列挙型の基底型を取得することができる。
<type_traits>のインクルードが必要。
#include <iostream>
#include <type_traits> //< underlying_type を使用するために必要
enum E1 : char
{ (略) };
enum class E2 : char
{ (略) };
enum E3
{ (略) };
enum class E4
{ (略) };
int main()
{
// E1 、E2 の基底型が char かどうかを判定
if( std::is_same<std::underlying_type<E1>::type, char>::value )
{
std::cout << "E1 の基底型は char" << std::end;
}
if( std::is_same<std::underlying_type<E2>::type, char>::value )
{
std::cout << "E2 の基底型は char" << std::end;
}
// E3 、E4 の基底型が整数型かどうかを判定
if( std::is_integral<std::underlying_type<E3>::type>::value )
{
std::cout << "E3 の基底型は整数型" << std::end;
}
if( std::is_integral<std::underlying_type<E4>::type>::value )
{
std::cout << "E4 の基底型は整数型" << std::end;
}
}
列挙型 T に対し、std::underlying_type<T>::type
で基底型を取得できる。
応用 : 列挙子を範囲ベースforで走査
列挙型にイテレータを追加することで、イテレーションを行えるようになる。
範囲ベースforに関してはリンク先を参照。
#include <type_traits>
// 列挙型定義
enum class Color
{
red, //< 先頭要素 == 0
blue,
LAST, //< end() が指す列挙子
}
// イテレータの定義
inline Color operator++(Color& x)
{
return static_cast<Color>(std::underlying_type<Color>::type(x) + 1);
}
inline Color operator*(Color c)
{
return c;
}
inline Color begin(Color r)
{
return static_cast<Color>(0);
}
inline Color end(Color r)
{
// LAST (有効な末尾要素の次)を返す
return Color::LAST;
}
int main()
{
// Color の全メンバを走査
for ( Color c : Color() )
{
doSomething(c);
}
}
以下の制約がある。
- 先頭の列挙子は必ず 0
- 以降の列挙子はひとつ前の要素に 1 を加算 ⇒ 初期値を指定しない
- 末尾に無効要素( LAST )を定義する必要がある ⇒ end() イテレータは「末尾要素の次」を表すため、LAST を追加しないと列挙子を末尾まで走査できない
余談
列挙型を定義する際、末尾要素の後ろにカンマが存在してもエラーとはならない。
このため、すべての列挙子で同様の書式をとることができる。